package yolo

import (
	"appengine"
	"appengine/taskqueue"
	"appengine/urlfetch"
	"bytes"
	"encoding/xml"
	"fmt"
	"net/http"
	"net/mail"
	"net/url"
	"regexp"
	"strconv"
	"time"
)

const (
	googleCodesiteURL = "code.google.com"

	googleCodesiteTimeLayout = "Wed Sep 28 15:04:05 2006"

	ActivityGoogleCodesiteReport = "GoogleCodesiteReport"
	ActivityGoogleCodesiteChange  = "GoogleCodesiteChange"
	ActivityGoogleCodesiteComment = "GoogleCodesiteComment"
	ActivityGoogleCodesiteCommit  = "GoogleCodesiteCommit"
	ActivityGoogleCodesiteOther   = "GoogleCodesiteOther"
)

var (
	activityGoogleCodesiteRegexp = regexp.MustCompile(`\((.*)\)(.*) (reported|changed|commented|commented|commited|pushed)( on)?$`)
	activityGoogleCodesiteContentRegexp = regexp.MustCompile(`<br/>Status: (.*) </pre>`)
)

type GoogleCodesiteLink struct {
	Rel  string `xml:"rel,attr"`
	Type string `xml:"type,attr"`
	Href string `xml:"href,attr"`
}

type GoogleCodesiteCategory struct {
	Term string "attr"
}

type GoogleCodesiteEntry struct {
	Updated    string
	Id         string
	Link       GoogleCodesiteLink
	Title      string
	AuthorName string "author>name"
	Category   GoogleCodesiteCategory
	Content    string
}

type GoogleCodesiteAtom struct {
	Updated string
	Entry   []GoogleCodesiteEntry
}

type GoogleCodesiteIssueUpdatesEntry struct {
	Updated    string `xml:"updated"`
	Id         string `xml:"id"`
	Link       GoogleCodesiteLink `xml:"link"`
	Title      string `xml:"title"`
	AuthorName string `xml:"author>name"`
	Content    string `xml:"content"`
}

type GoogleCodesiteIssueUpdatesAtom struct {
	XMLName xml.Name "http://www.w3.org/2005/Atom";
	Updated string   `xml:"updated"`
	Entry   []GoogleCodesiteIssueUpdatesEntry `xml:"entry"`
}

type GoogleCodesite struct {
	*url.URL
}

func init() {
	RegisterPollerWithArguments(SiteGoogleCodesite, NewGoogleCodesitePoller)
	RegisterMailProcessor(&GoogleCodesiteMailProcessor{})
}

func NewGoogleCodesitePoller(pc *PollContext, args []string) Poller {
	poller := &GoogleCodesitePoller{pc, args[0]}
	return poller
}

func NewGoogleCodesite(path string) *GoogleCodesite {
	url_, _ := url.Parse(path)
	return &GoogleCodesite{URL: url_}
}

func parseGoogleCodesiteTitle(title string) (project, issue GoogleCodesiteLink, summary, activityType string) {
	var t struct {
		A    []GoogleCodesiteLink
		Text string "chardata"
	}
	err := xml.Unmarshal([]byte("<p>"+title+"</p>"), &t)
	if err != nil {
		panic(err)
	}
	if len(t.A) >= 2 {
		project = t.A[0]
		issue = t.A[1]
	}
	if m := activityGoogleCodesiteRegexp.FindStringSubmatch(t.Text); m != nil {
		summary = m[1]
		switch m[3] {
		case "reported":
			activityType = ActivityGoogleCodesiteReport
		case "changed":
			activityType = ActivityGoogleCodesiteChange
		case "commented":
			activityType = ActivityGoogleCodesiteComment
		case "committed":
			activityType = ActivityGoogleCodesiteCommit
		default:
			activityType = ActivityGoogleCodesiteOther
		}
	} else {
		summary = t.Text
		activityType = ActivityGoogleCodesiteOther
	}
	return
}

func (gc *GoogleCodesite) fetchUser(c *http.Client, user string) (activities []*ActivityData, err error) {
	resp, err := c.Get(fmt.Sprintf("%s/feeds/u/%s/updates/user/basic", gc.URL.String(), user))
	if err != nil {
		return activities, err
	}
	var atom GoogleCodesiteAtom
	err = xml.NewDecoder(resp.Body).Decode(&atom)
	if err != nil {
		return activities, err
	}
	for _, entry := range atom.Entry {
		update, err := time.Parse(time.RFC3339, entry.Updated)
		if err != nil {
			update = time.Now()
		}
		title := entry.Title
		_, issue, summary, atype := parseGoogleCodesiteTitle(title)
		a := new(ActivityData)
		a.Activity.SiteURL = googleCodesiteURL
		a.Activity.User = user
		a.Activity.Time = update
		a.Activity.URL = issue.Href
		a.Activity.Snippet = summary + "\n" + entry.Content
		a.Activity.Type = atype

		a.ActivityRecipients.Recipients = append(a.ActivityRecipients.Recipients, user)
		activities = append(activities, a)
	}
	return activities, nil
}

func postStartCheckGoogleCodesite(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	user := r.FormValue("user")
	c.Debugf("will check user:%s", user)
	_, err := postTask(c, "/worker/google_codesite_start_check", "default",
		PostParams{"user": {user}})
	if err != nil {
		c.Warningf("post err:%s", err)
	}
	http.Redirect(w, r, "/", http.StatusFound)
}

func startCheckGoogleCodesite(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	user := r.FormValue("user")
	if user == "" {
		c.Warningf("no user")
		return
	}
	err := checkGoogleCodesite(c, user)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	t := taskqueue.NewPOSTTask("/worker/google_codesite_start_check",
		map[string][]string{"user": {user}})
	t.Name = fmt.Sprintf("check-google-code-site-%s-%s", user, (time.Now().Format("2006-01-02T15-04")))
	t.Delay = time.Duration(60 * time.Minute) // 1 min
	_, err = taskqueue.Add(c, t, "default")
	if err != nil {
		c.Warningf("post err:%s", err)
	}
}

func parseGoogleCodesiteProjectIssueUpdatesContent(content string) (activityType string) {
	if m := activityGoogleCodesiteContentRegexp.FindStringSubmatch(content); m != nil {
		activityType = fmt.Sprintf("GoogleCodesite%s", m[1])
	} else {
		activityType = "GoogleCodesiteOther"
	}
	return
}

func (gc *GoogleCodesite) fetchProjectIssueUpdates(c *http.Client, project string) (activities []*ActivityData, err error) {
	resp, err := c.Get(fmt.Sprintf("%s/feeds/p/%s/issueupdates/basic", gc.URL.String(), project))
	if err != nil {
		return activities, err
	}
	var atom GoogleCodesiteIssueUpdatesAtom
	err = xml.NewDecoder(resp.Body).Decode(&atom)
	if err != nil {
		return activities, err
	}
	for _, entry := range atom.Entry {
		update, err := time.Parse(time.RFC3339, entry.Updated)
		if err != nil {
			update = time.Now()
		}
		content := entry.Content
		atype := parseGoogleCodesiteProjectIssueUpdatesContent(content)
		a := new(ActivityData)
		a.Activity.SiteURL = googleCodesiteURL
		// If the user obfscate his/her email address, following AuthorName is also obfscated.
		a.Activity.User = entry.AuthorName
		a.Activity.Time = update
		a.Activity.URL = entry.Link.Href
		a.Activity.Snippet = entry.Title + "\n" + entry.Content
		a.Activity.Type = atype

		a.ActivityRecipients.Recipients = append(a.ActivityRecipients.Recipients, entry.AuthorName)
		activities = append(activities, a)
	}
	return activities, nil
}

func checkGoogleCodesite(c appengine.Context, project string) (err error) {
	c.Debugf("check: %s %s", googleCodesiteURL, project)

	gc := NewGoogleCodesite("http://" + googleCodesiteURL + "/")

	activities, err := gc.fetchProjectIssueUpdates(urlfetch.Client(c), project)
	if err != nil {
		return err
	}
	return RecordActivityDataMulti(c, activities)
}

// MailProcessor
type GoogleCodesiteMailProcessor struct {}

func (this *GoogleCodesiteMailProcessor) ShouldProcess(c appengine.Context, msg *mail.Message) bool {
	if msg.Header.Get("X-Generated-By") != "Google Code" {
		return false
	}
	if msg.Header.Get("X-GoogleCode-Project") == "" {
		return false
	}
	if msg.Header.Get("X-GoogleCode-Issue-Id") == "" {
		return false
	}
	return true
}

func (this *GoogleCodesiteMailProcessor) Process(c appengine.Context, msg *mail.Message)  {
	var bodyBuf bytes.Buffer
	if _, err := bodyBuf.ReadFrom(msg.Body); err != nil {
		c.Errorf("cannot read body: %v", err)
		return
	}

	user := msg.Header.Get("From")
	recipient := msg.Header.Get("To")
	a := new(ActivityData)
	a.Activity.SiteURL = googleCodesiteURL
	a.Activity.User = user
	if time, timeerr := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", msg.Header.Get("Date")); timeerr == nil {
		a.Activity.Time = time
	} else {
		c.Errorf("Cannot parse date: %v", timeerr)
		return
	}
	a.Activity.Snippet = msg.Header.Get("Subject")
	a.Activity.Type = ActivityGoogleCodesiteComment
	a.ActivityRecipients.Recipients = append(a.ActivityRecipients.Recipients, user)
	a.ActivityRecipients.Recipients = append(a.ActivityRecipients.Recipients, recipient)
	RecordActivityData(c, a)
}

// Poller ---
type GoogleCodesitePoller struct {
	*PollContext
	project string
}

func (poller *GoogleCodesitePoller) Start(c appengine.Context) error {
	// TODO: implement duration and retry.
	return poller.postFetchTask(c, 10, time.Duration(10))
}

func (poller *GoogleCodesitePoller) Delete(c appengine.Context) error {
	// will not implement.
	return nil
}

func (poller *GoogleCodesitePoller) RunTask(c appengine.Context, r *http.Request) error {
	project := poller.project
	if project == "" {
		return NewYoloError("GoogleCodesitePoller.Runtask(): project needs to be specified.")
	}
	return checkGoogleCodesite(c, project)
}

func (poller *GoogleCodesitePoller) postFetchTask(c appengine.Context, retry int, delay time.Duration) error {
	return poller.PostTask(c, "", delay,
		map[string][]string{
		"retry": {strconv.FormatInt(int64(retry), 10)}})
}

// Issue handler ---
type GoogleCodesiteIssueHandler struct {
	SiteURL string
}

func NewGoogleCodesiteIssueHandler(siteURL string) IssueHandler {
	handler := &GoogleCodesiteIssueHandler{siteURL}
	return handler
}

func (handler *GoogleCodesiteIssueHandler) LoadDetail(c appengine.Context, issueId string, detail* IssueDetail) error {
	// TODO: implement.
	return nil;
}
